package com.example.activiti.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.example.activiti.common.MessageBody;
import com.example.activiti.config.UserLocal;
import com.example.activiti.model.ActTask;
import com.example.activiti.model.LeaveDTO;
import com.example.activiti.model.ProcessModelVO;
import com.example.activiti.service.ProcessService;
import com.example.activiti.utils.WorkflowUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.*;
import org.activiti.engine.form.FormProperty;
import org.activiti.engine.form.StartFormData;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.activiti.engine.impl.context.Context;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.DeploymentBuilder;
import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.image.ProcessDiagramGenerator;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import sun.misc.BASE64Encoder;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.zip.ZipInputStream;

/**
 * @author Mr.yuan
 * @version v1.0
 * @date 2019/11/12 11:19
 **/
@Service
public class ProcessServiceImpl implements ProcessService {

    @Resource
    private ObjectMapper objectMapper;
    @Resource
    private TaskService taskService;
    @Resource
    private RuntimeService runtimeService;
    @Resource
    private RepositoryService repositoryService;
    @Resource
    private IdentityService identityService;
    @Resource
    private HistoryService historyService;
    @Resource
    private ProcessEngine processEngine;
    @Resource
    private FormService formService;

    @Override
    public List<ProcessModelVO> findProcessList() {

        List<ProcessDefinition> processDefinitionList = repositoryService.createProcessDefinitionQuery().list();

        List<ProcessModelVO> processModelList = new ArrayList<>();
        ProcessModelVO modelVO;
        for (ProcessDefinition processDefinition : processDefinitionList) {
            modelVO = new ProcessModelVO();
            modelVO.setId(processDefinition.getId());
            modelVO.setKey(processDefinition.getKey());
            modelVO.setName(processDefinition.getName());
            modelVO.setVersion(processDefinition.getVersion());
            modelVO.setCategory(processDefinition.getCategory());
            modelVO.setTenantId(processDefinition.getTenantId());
            modelVO.setStatus(processDefinition.isSuspended());
            modelVO.setDescription(processDefinition.getDescription());
            modelVO.setDiagramResourceName(processDefinition.getDiagramResourceName());
            ProcessInstance instance = runtimeService.createProcessInstanceQuery().processDefinitionId(processDefinition.getId()).singleResult();
            if (Objects.nonNull(instance)) {
                modelVO.setProcessInstanceId(instance.getId());
            }
            processModelList.add(modelVO);
        }
        return processModelList;
    }

    @Override
    public String createProcessModel() {
        //初始化一个空模型
        Model modelData = repositoryService.newModel();
        //设置一些默认信息
        String name = "新建流程模型";
        String description = "新建流程模型";
        int revision = 1;
        String key = "new process";

        ObjectNode modelObjectNode = objectMapper.createObjectNode();
        modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME, name);
        modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, revision);
        modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
        modelData.setMetaInfo(modelObjectNode.toString());
        modelData.setName(name);
        modelData.setKey(StringUtils.defaultString(key));
        repositoryService.saveModel(modelData);

        BpmnModel bpmnModel = new BpmnModel();
//        Process process = new Process();
//        process.setId("new process");
//        process.setName("新建流程");
//        bpmnModel.addProcess(process);
        BpmnJsonConverter converter = new BpmnJsonConverter();
        ObjectNode objectNode = converter.convertToJson(bpmnModel);
        repositoryService.addModelEditorSource(modelData.getId(), objectNode.toString().getBytes(StandardCharsets.UTF_8));


        Map<String, String> map = new HashMap<>(3);
        map.put("modelId", modelData.getId());
        map.put("name", modelData.getName());
        return JSON.toJSONString(map);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deployModel(String modelId) throws IOException {
        Deployment deployment;
        if (StringUtils.isNotBlank(modelId)) {
            //获取模型
            Model modelData = repositoryService.getModel(modelId);
            byte[] bytes = repositoryService.getModelEditorSource(modelData.getId());
            if (bytes == null) {
                throw new RuntimeException("模型数据为空，请先设计流程并成功保存，再进行发布");
            }
            JsonNode modelNode = new ObjectMapper().readTree(bytes);
            BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode);
            if (model.getProcesses().size() == 0) {
                throw new RuntimeException("数据模型不符要求，请至少设计一条主线流程");
            }
            byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(model);
            //发布流程
            String processName = modelData.getKey() + ".bpmn";
            deployment = repositoryService.createDeployment()
                    .name(modelData.getName())
                    .category(modelData.getCategory())
                    .tenantId(modelData.getTenantId())
                    .addString(processName, new String(bpmnBytes, StandardCharsets.UTF_8))
                    .deploy();
            modelData.setVersion(modelData.getVersion() + 1);
            modelData.setDeploymentId(deployment.getId());
            repositoryService.saveModel(modelData);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void processUpload(MultipartFile file) throws Exception {
        // 获取上传的文件名
        String fileName = file.getOriginalFilename();

        // 得到输入流（字节流）对象
        InputStream fileInputStream = file.getInputStream();

        // 文件的扩展名
        String extension = FilenameUtils.getExtension(fileName);

        // zip或者bar类型的文件用ZipInputStream方式部署
        DeploymentBuilder deploymentBuilder = repositoryService.createDeployment();
        if (Objects.equals(extension, "zip") || Objects.equals(extension, "bar")) {
            ZipInputStream zip = new ZipInputStream(fileInputStream);
            deploymentBuilder.addZipInputStream(zip);
        } else {
            // 其他类型的文件直接部署
            deploymentBuilder.addInputStream(fileName, fileInputStream);
        }
        Deployment deployment = deploymentBuilder.name("员工请假流").deploy();
        convertToModel(deployment.getId());
//        if (Objects.nonNull(file)) {
//            String fileName = file.getOriginalFilename();
//            try {
//                InputStream fileInputStream = file.getInputStream();
//                String extension = FilenameUtils.getExtension(fileName);
//                if (StringUtils.equals(extension, "zip") || StringUtils.equals(extension, "bar")) {
//                    ZipInputStream zip = new ZipInputStream(fileInputStream);
//                    deployment = repositoryService.createDeployment().addZipInputStream(zip).deploy();
//                } else {
//                    deployment = repositoryService.createDeployment().addInputStream(fileName, fileInputStream).name(fileName).deploy();
//                }
//                List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).list();
//
//                for (ProcessDefinition processDefinition : list) {
//                    WorkflowUtils.exportDiagramToFile(repositoryService, processDefinition);
//                }
//
//            } catch (Exception e) {
//                log.error("error on deploy process, because of file input stream", e);
//            }
//        }
    }

    @Override
    public void submitProcess(LeaveDTO param) {
        String proId = param.getKey();
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(proId).singleResult();
        boolean hasStartFormKey = processDefinition.hasStartFormKey();

        Map<String, String> formValues = new HashMap<>(6);

        JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(param));
        if (hasStartFormKey) {
            // formKey表单
            for (String key : jsonObject.keySet()) {
                formValues.put(key, jsonObject.getString(key));
            }
        } else {
            // 动态表单
            // 先读取表单字段在根据表单字段的ID读取请求参数值
            StartFormData formData = formService.getStartFormData(proId);

            // 从请求中获取表单字段的值
            List<FormProperty> formProperties = formData.getFormProperties();
            for (FormProperty formProperty : formProperties) {
                String value = jsonObject.getString(formProperty.getId());
                formValues.put(formProperty.getId(), value);
            }
        }

        identityService.setAuthenticatedUserId(param.getApplyUserId());

        // 提交表单字段并启动一个新的流程实例
        formService.submitStartFormData(proId, formValues);
    }

    @Override
    public void suspendTask(String processInstanceId) {
        runtimeService.suspendProcessInstanceById(processInstanceId);
    }

    @Override
    public void activeTask(String processInstanceId) {
        runtimeService.activateProcessInstanceById(processInstanceId);
    }

    @Override
    public List<HistoricTaskInstance> findOrganizerTasks(String organizer) {
        return historyService.createHistoricTaskInstanceQuery().taskAssignee(organizer).list();
    }

    @Override
    public List<ActTask> findHandleTasks(String assignee) {
        List<ActTask> entities = new ArrayList<>();
        List<Task> list = taskService.createTaskQuery().taskCandidateOrAssigned(assignee).list();
        ActTask entity;
        for (Task task : list) {
            entity = new ActTask();
            entity.setId(task.getId());
            entity.setAssignee(task.getAssignee());
            String applyUser = historyService.createHistoricProcessInstanceQuery().
                    processInstanceId(task.getProcessInstanceId()).singleResult().getStartUserId();
            ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().processDefinitionId(task.getProcessDefinitionId()).singleResult();
            entity.setName(definition.getName());
            entity.setDescription(definition.getDescription());
            entity.setApplyUser(applyUser);
            entities.add(entity);
        }
        return entities;
    }

    @Override
    public void claim(String taskId) {
        taskService.claim(taskId, UserLocal.getUser().getId());
    }

    @Override
    public List<Model> findModelList() {
        List<Model> modelList = repositoryService.createModelQuery().list();
        for (Model model : modelList) {
            if (StringUtils.isBlank(model.getDeploymentId())) {
                model.setCategory("0");
            } else {
                model.setCategory("1");
            }
        }
        return modelList;
    }

    @Override
    public Object getLeaveType() {
        List<ProcessDefinition> definitionList = repositoryService.createProcessDefinitionQuery().active().list();
        JSONArray array = new JSONArray();
        JSONObject object;
        for (ProcessDefinition definition : definitionList) {
            object = new JSONObject();
            object.put("id", definition.getId());
            object.put("name", definition.getName());
            array.add(object);
        }
        return array;
    }

    @Override
    public void export(String deploymentId) throws IOException {
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deploymentId).singleResult();
        //xml文件
        String resourceName = processDefinition.getResourceName();
        InputStream resourceInputStream = repositoryService.getResourceAsStream(deploymentId, resourceName);

        //png文件
        String diagramResourceName = processDefinition.getDiagramResourceName();
        InputStream diagramInputStream = repositoryService.getResourceAsStream(deploymentId, diagramResourceName);

        String xmlPath = WorkflowUtils.saveResource(resourceInputStream, resourceName);
        String pngPath = WorkflowUtils.saveResource(diagramInputStream, diagramResourceName);

        List<String> list = new ArrayList<>();
        list.add(xmlPath);
        list.add(pngPath);
        String packageName = resourceName.substring(0, resourceName.indexOf("."));
        WorkflowUtils.compress(list, packageName, false);
    }

    @Override
    public List<Task> findCandidateTasks(String userId) {
        List<Task> list = taskService.createTaskQuery().taskAssignee(userId).active().list();
        for (Task task : list) {
            String applyUser = historyService.createHistoricProcessInstanceQuery().
                    processInstanceId(task.getProcessInstanceId()).startedBy(userId).singleResult().getStartUserId();
            task.setOwner(applyUser);
        }
        return list;
    }

    @Override
    public List<HistoricTaskInstance> findHistoryTasks(String assignee) {
        return historyService.createHistoricTaskInstanceQuery()
                .taskAssignee(assignee)
                .finished()
                .list();
    }

    @Override
    public void taskTrace(String processInstanceId, HttpServletResponse response) throws IOException {
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        //查询历史节点
        List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(processInstanceId).orderByHistoricActivityInstanceId().asc().list();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
        List<String> activeActivityIds = runtimeService.getActiveActivityIds(processInstanceId);
        List<String> executedFlowIdList = new ArrayList<>();

        for (int i = 0; i < historicActivityInstanceList.size() - 1; i++) {
            HistoricActivityInstance hai = historicActivityInstanceList.get(i);
            FlowNode flowNode = (FlowNode) bpmnModel.getFlowElement(hai.getActivityId());
            List<SequenceFlow> sequenceFlows = flowNode.getOutgoingFlows();
            if (sequenceFlows.size() > 1) {
                HistoricActivityInstance nextHai = historicActivityInstanceList.get(i + 1);
                sequenceFlows.forEach(sequenceFlow -> {
                    if (sequenceFlow.getTargetRef().equals(nextHai.getActivityId())) {
                        executedFlowIdList.add(sequenceFlow.getId());
                    }
                });
            } else {
                executedFlowIdList.add(sequenceFlows.get(0).getId());
            }
        }

        ProcessEngineConfiguration processEngineConfiguration = processEngine.getProcessEngineConfiguration();
        Context.setProcessEngineConfiguration((ProcessEngineConfigurationImpl) processEngineConfiguration);

        ProcessDiagramGenerator diagramGenerator = processEngineConfiguration.getProcessDiagramGenerator();
        InputStream imageStream = diagramGenerator.generateDiagram(
                bpmnModel, "png", activeActivityIds, executedFlowIdList,
                "宋体", "宋体", "宋体", null, 1.0);

        byte[] buffer = new byte[1024];
        int length;
        while ((length = imageStream.read(buffer)) != -1) {
            response.getOutputStream().write(buffer, 0, length);
        }
    }

    @Override
    public MessageBody processView(String processDefinitionId, String resourceName, HttpServletResponse response) throws IOException {

        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processDefinitionId(processDefinitionId).singleResult();
        List<String> activeActivityIds = runtimeService.getActiveActivityIds(processInstance.getId());
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
        // 通过接口读取
        InputStream resourceAsStream = processEngine.getProcessEngineConfiguration().getProcessDiagramGenerator().generateDiagram(
                bpmnModel, "png", new ArrayList<>(), new ArrayList<>(),
                "宋体", "宋体", "宋体", null, 1.0);

        // 输出资源内容到相应对象
        byte[] b = new byte[resourceAsStream.available()];
        resourceAsStream.read(b, 0, b.length);
        BASE64Encoder encoder = new BASE64Encoder();
        String resource = "data:image/png;base64," + encoder.encode(b);
        System.out.println(resource);

        return MessageBody.success(resource);
    }


    private void convertToModel(String deploymentId) throws Exception {
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .deploymentId(deploymentId).singleResult();
        InputStream bpmnStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(),
                processDefinition.getResourceName());
        XMLInputFactory xif = XMLInputFactory.newInstance();
        InputStreamReader in = new InputStreamReader(bpmnStream, StandardCharsets.UTF_8);
        XMLStreamReader xtr = xif.createXMLStreamReader(in);
        BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xtr);

        BpmnJsonConverter converter = new BpmnJsonConverter();
        ObjectNode modelNode = converter.convertToJson(bpmnModel);
        Model modelData = repositoryService.newModel();
        modelData.setKey(processDefinition.getKey());
        modelData.setName(processDefinition.getName());
        modelData.setCategory(processDefinition.getCategory());

        ObjectNode modelObjectNode = new ObjectMapper().createObjectNode();
        modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME, processDefinition.getName());
        modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, 1);
        modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, processDefinition.getDescription());
        modelData.setMetaInfo(modelObjectNode.toString());

        modelData.setDeploymentId(deploymentId);
        repositoryService.saveModel(modelData);

        repositoryService.addModelEditorSource(modelData.getId(), modelNode.toString().getBytes(StandardCharsets.UTF_8));
    }
}
